package MA_Projekt_Client_SL;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerPNames;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;

import de.unidue.masterproject.ccc.exceptions.WebServiceException;


abstract public class DefaultRESTController {

	protected static String userName = "";
	protected static String password = "";

	protected HttpClient httpClient;

	// default baseurl is defined by a constant in ApplicationConfig but can be
	// overwritten
	protected String baseUrl = WebserviceConfig.BASE_URL;

	protected boolean withAuthorization = true;

	// Use generic data type instead of concrete User object
	public <T> T httpGet(String resource, Class<T> entityClass)
			throws WebServiceException {

		InputStream instream = getHttpGetInputStream(resource);
		return getObject(instream, entityClass);
	}

	public <T> List<T> httpGetCollection(String resource, Class<T> entityClass)
			throws WebServiceException {
		InputStream instream = getHttpGetInputStream(resource);
		return getObjectCollection(instream, entityClass);
	}

	// TODO performance optimization: cast collection in here and not outside
	public <T> List<T> getObjectCollection(InputStream instream,
			Class<T> entityClass) throws WebServiceException {

		ArrayList<T> resultArray = null;

		try {
			ObjectMapper mapper = new ObjectMapper();
			resultArray = mapper.readValue(instream,
					TypeFactory.collectionType(ArrayList.class, entityClass));

		} catch (JsonParseException ex) {
			ex.printStackTrace();
			throw new WebServiceException(402);

		} catch (JsonMappingException ex) {
			ex.printStackTrace();
			throw new WebServiceException(402);

		} catch (IOException ex) {
			ex.printStackTrace();
			throw new WebServiceException(402);

		} finally {
			try {
				instream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		return resultArray;
	}

	public <T> T getObject(InputStream instream, Class<T> entityClass)
			throws WebServiceException {

		T returnObject = null;

		try {
			ObjectMapper mapper = new ObjectMapper();
			returnObject = mapper.readValue(instream, entityClass);

		} catch (JsonParseException e) {
			e.printStackTrace();
			throw new WebServiceException(402);

		} catch (JsonMappingException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);

		} finally {
			try {
				instream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return returnObject;
	}

	public <T> T httpPostWithResult(String resource, T object,
			Class<T> entityClass) throws WebServiceException {

		InputStream instream = getHttpPostInputStream(resource, object);
		return getObject(instream, entityClass);
	}

	// Needs to be refactored
	public <T> String httpPost(String resource, T object, Class<T> entityClass)
			throws WebServiceException {

		String url = baseUrl + resource;
		// httpClient = new DefaultHttpClient();
		httpClient = getDefaultHttpsClientWithCertifateCheckOff();

		HttpPost httpPost = new HttpPost(url);
		ObjectMapper mapperPost = new ObjectMapper();
		String mappedObject;
		try {
			mappedObject = mapperPost.writeValueAsString(object);
			HttpEntity objectEntity = new StringEntity(mappedObject);
			httpPost.setEntity(objectEntity);

		} catch (JsonParseException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (JsonMappingException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		}

		if (withAuthorization) {
			setAuthHeader(httpPost);
		}

		httpPost.setHeader("Accept", "application/json");
		httpPost.setHeader("Content-type", "application/json");
		return executeCreate(httpPost);
	}

	// Needs to be refactored
	public <T> String httpPutCreate(String resource, T object,
			Class<T> entityClass) throws WebServiceException {

		String url = baseUrl + resource;
		// httpClient = new DefaultHttpClient();
		httpClient = getDefaultHttpsClientWithCertifateCheckOff();

		HttpPut httpPut = new HttpPut(url);
		ObjectMapper mapperPost = new ObjectMapper();
		String mappedObject;
		try {
			mappedObject = mapperPost.writeValueAsString(object);
			HttpEntity objectEntity = new StringEntity(mappedObject);
			httpPut.setEntity(objectEntity);

		} catch (JsonParseException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (JsonMappingException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		}

		if (withAuthorization) {
			setAuthHeader(httpPut);
		}

		httpPut.setHeader("Accept", "application/json");
		httpPut.setHeader("Content-type", "application/json");

		return executeCreate(httpPut);

	}

	// Needs to be refactored
	public <T> T httpPutUpdate(String resource, T object, Class<T> entityClass)
			throws WebServiceException {

		String url = baseUrl + resource;
		// httpClient = new DefaultHttpClient();
		httpClient = getDefaultHttpsClientWithCertifateCheckOff();

		HttpPut httpPut = new HttpPut(url);
		ObjectMapper mapperPost = new ObjectMapper();
		String mappedObject;
		try {
			mappedObject = mapperPost.writeValueAsString(object);
			HttpEntity objectEntity = new StringEntity(mappedObject);
			httpPut.setEntity(objectEntity);

		} catch (JsonParseException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (JsonMappingException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		}

		if (withAuthorization) {
			setAuthHeader(httpPut);
		}

		httpPut.setHeader("Accept", "application/json");
		httpPut.setHeader("Content-type", "application/json");

		HttpEntity entity = executeUpdate(httpPut);
		T returnObject = null;
		if (entity != null) {

			InputStream instream = null;

			try {
				instream = entity.getContent();
				ObjectMapper mapper = new ObjectMapper();
				returnObject = mapper.readValue(instream, entityClass);

			} catch (JsonParseException e) {
				e.printStackTrace();
				throw new WebServiceException(402);

			} catch (JsonMappingException e) {
				e.printStackTrace();
				throw new WebServiceException(402);

			} catch (IOException e) {
				e.printStackTrace();
				throw new WebServiceException(402);

			} finally {
				try {
					instream.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		return returnObject;
	}

	// Needs to be refactored
	public <T> void httpDelete(String resource, Class<T> entityClass)
			throws WebServiceException {

		String url = baseUrl + resource;
		// httpClient = new DefaultHttpClient();
		httpClient = getDefaultHttpsClientWithCertifateCheckOff();

		HttpDelete httpDelete = new HttpDelete(url);

		if (withAuthorization) {
			setAuthHeader(httpDelete);
		}

		httpDelete.setHeader("Accept", "application/json");
		httpDelete.setHeader("Content-type", "application/json");
		executeDelete(httpDelete);
	}

	/**
	 * 
	 * Execute methods
	 * 
	 * 
	 **/
	// Needs to be refactored
	private <T> void executeDelete(HttpUriRequest req) throws WebServiceException {
		HttpResponse response;

		try {
			response = httpClient.execute(req);
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			throw new WebServiceException(402);

		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		}

		if (response == null) {
			throw new WebServiceException(402);
		}

		int statusCode = response.getStatusLine().getStatusCode();

		// check for errors and throw convert to CustomException
		switch (statusCode) {
		// Successful 2xx -> in all cases return entity
		case 200:
			; // OK
		case 201:
			// Created
		case 202:
			; // Accepted
		case 204:
			return; // No Content

			// Redirection 3xx -> in all cases throw an PLException

		case 300:
			; // Multiple choices
		case 301:
			; // Moved permanently
		case 302:
			; // Found
		case 303:
			; // See other
		case 304:
			throw new WebServiceException(100); // Not modified

			// Client error 4xx

		case 400:
			throw new WebServiceException(103); // Bad Request
		case 401:
			throw new WebServiceException(401); // Unauthorized
		case 403:
			throw new WebServiceException(401); // Forbidden
		case 404:
			throw new WebServiceException(102); // Not found
		case 405:
			; // Method not allowed
		case 406:
			throw new WebServiceException(100); // Not acceptable
		case 409:
			throw new WebServiceException(104); // Conflict

			// Server error 5xx

		case 500:
			; // Internal server error
		case 501:
			; // Not implemented
		case 503:
			; // Service unavailable

		default:
			throw new WebServiceException(100);

		}
	}

	// TODO : Needs to be refactored
	protected <T> String executeCreate(HttpUriRequest req)
			throws WebServiceException {
		HttpResponse response;

		try {
			response = httpClient.execute(req);
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			throw new WebServiceException(402);

		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		}

		if (response == null) {
			throw new WebServiceException(402);
		}

		int statusCode = response.getStatusLine().getStatusCode();

		String url = "";
		// check for errors and throw convert to CustomException
		switch (statusCode) {
		// Successful 2xx -> in all cases return entity
		case 200:
			; // OK
		case 201:
			Header[] header = response.getHeaders("Location");
			if (header.length != 0) {
				url = header[0].getValue();
			}
			// Created
		case 202:
			; // Accepted
		case 204:
			return url; // No Content

			// Redirection 3xx -> in all cases throw an PLException

		case 300:
			; // Multiple choices
		case 301:
			; // Moved permanently
		case 302:
			; // Found
		case 303:
			; // See other
		case 304:
			throw new WebServiceException(100); // Not modified

			// Client error 4xx

		case 400:
			throw new WebServiceException(103); // Bad Request
		case 401:
			throw new WebServiceException(401); // Unauthorized
		case 403:
			throw new WebServiceException(104); // Forbidden
		case 404:
			throw new WebServiceException(102); // Not found
		case 405:
			; // Method not allowed
		case 406:
			throw new WebServiceException(100); // Not acceptable
		case 409:
			// TODO:
			throw new WebServiceException(104); // Conflict

			// Server error 5xx

		case 500:
			; // Internal server error
		case 501:
			; // Not implemented
		case 503:
			; // Service unavailable

		default:
			throw new WebServiceException(100);

		}
	}

	// TODO : Needs to be refactored
	protected <T> HttpEntity executeUpdate(HttpUriRequest req)
			throws WebServiceException {
		HttpResponse response;

		try {
			response = httpClient.execute(req);
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			throw new WebServiceException(402);

		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		}

		if (response == null) {
			throw new WebServiceException(402);
		}

		int statusCode = response.getStatusLine().getStatusCode();
		HttpEntity entity = response.getEntity();

		// check for errors and throw convert to CustomException
		switch (statusCode) {
		// Successful 2xx -> in all cases return entity
		case 200:
			; // OK
		case 201:
			// Created
		case 202:
			; // Accepted
		case 204:
			return entity; // No Content

			// Redirection 3xx -> in all cases throw an PLException

		case 300:
			; // Multiple choices
		case 301:
			; // Moved permanently
		case 302:
			; // Found
		case 303:
			; // See other
		case 304:
			throw new WebServiceException(100); // Not modified

			// Client error 4xx

		case 400:
			throw new WebServiceException(103); // Bad Request
		case 401:
			throw new WebServiceException(401); // Unauthorized
		case 403:
			throw new WebServiceException(401); // Forbidden
		case 404:
			throw new WebServiceException(102); // Not found
		case 405:
			; // Method not allowed
		case 406:
			throw new WebServiceException(100); // Not acceptable
		case 409:
			throw new WebServiceException(104); // Conflict

			// Server error 5xx

		case 500:
			; // Internal server error
		case 501:
			; // Not implemented
		case 503:
			; // Service unavailable

		default:
			throw new WebServiceException(100);

		}
	}

	public HttpEntity httpGetResponse(String resource) throws WebServiceException {

		String url = baseUrl + resource;
		// httpClient = new DefaultHttpClient();
		httpClient = getDefaultHttpsClientWithCertifateCheckOff();

		HttpGet httpget = new HttpGet(url);

		if (withAuthorization) {
			setAuthHeader(httpget);
		}

		HttpEntity entity = execute(httpget);

		return entity;

	}

	// Needs to be refactored
	protected HttpEntity execute(HttpUriRequest req) throws WebServiceException {
		HttpResponse response;

		try {
			response = httpClient.execute(req);
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			throw new WebServiceException(402);

		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		}

		if (response == null) {
			throw new WebServiceException(402);
		}

		int statusCode = response.getStatusLine().getStatusCode();
		HttpEntity entity = response.getEntity();

		// check for errors and throw convert to CustomException
		switch (statusCode) {

		// Successful 2xx -> in all cases return entity

		case 200:
			; // OK
		case 201:
			; // Created
		case 202:
			; // Accepted
		case 204:
			return entity; // No Content

			// Redirection 3xx -> in all cases throw an PLException

		case 300:
			; // Multiple choices
		case 301:
			; // Moved permanently
		case 302:
			; // Found
		case 303:
			; // See other
		case 304:
			throw new WebServiceException(100); // Not modified

			// Client error 4xx

		case 400:
			throw new WebServiceException(103); // Bad Request
		case 401:
			throw new WebServiceException(401); // Unauthorized
		case 403:
			throw new WebServiceException(401); // Forbidden
		case 404:
			throw new WebServiceException(102); // Not found
		case 405:
			; // Method not allowed
		case 406:
			throw new WebServiceException(100); // Not acceptable
		case 409:
			throw new WebServiceException(104); // Conflict

			// Server error 5xx

		case 500:
			; // Internal server error
		case 501:
			; // Not implemented
		case 503:
			; // Service unavailable

		default:
			throw new WebServiceException(100);

		}
	}

	private InputStream getHttpGetInputStream(String resource)
			throws WebServiceException {

		String url = baseUrl + resource;

		// httpClient = new DefaultHttpClient();
		httpClient = getDefaultHttpsClientWithCertifateCheckOff();

		HttpGet httpget = new HttpGet(url);

		if (withAuthorization) {
			setAuthHeader(httpget);
		}

		HttpEntity entity = execute(httpget);

		if (entity != null) {
			InputStream instream = null;
			try {
				instream = entity.getContent();
			} catch (Exception e1) {
				e1.printStackTrace();
				return null;
			}
			return instream;
		} else {
			return null;
		}
	}

	private <T> InputStream getHttpPostInputStream(String resource, T object)
			throws WebServiceException {

		String url = baseUrl + resource;

		// httpClient = new DefaultHttpClient();
		httpClient = getDefaultHttpsClientWithCertifateCheckOff();

		HttpPost httpPost = new HttpPost(url);

		ObjectMapper mapperPost = new ObjectMapper();
		String mappedObject;
		try {
			mappedObject = mapperPost.writeValueAsString(object);
			HttpEntity objectEntity = new StringEntity(mappedObject);
			httpPost.setEntity(objectEntity);
		} catch (JsonParseException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (JsonMappingException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		} catch (IOException e) {
			e.printStackTrace();
			throw new WebServiceException(402);
		}

		if (withAuthorization) {
			setAuthHeader(httpPost);
		}

		httpPost.setHeader("Accept", "application/json");
		httpPost.setHeader("Content-type", "application/json");

		HttpEntity entity = execute(httpPost);

		if (entity != null) {
			InputStream instream = null;
			try {
				instream = entity.getContent();
			} catch (Exception e1) {
				e1.printStackTrace();
				return null;
			}
			return instream;
		} else {
			return null;
		}
	}

	private void setAuthHeader(HttpRequestBase rb) {
		String pw = Base64.encodeBase64String((userName + ":" + password).getBytes())
				.toString().trim();
		rb.setHeader("Authorization", "Basic " + pw);
	}

	/*
	 * WARNING: Don't use this in production environment Debug convenience
	 * method that accesses HTTPS resource without checking the cert
	 */
	private DefaultHttpClient getDefaultHttpsClientWithCertifateCheckOff() {
		SchemeRegistry schemeRegistry = new SchemeRegistry();
		schemeRegistry.register(new Scheme("http", PlainSocketFactory
				.getSocketFactory(), 80));
		// schemeRegistry.register(new Scheme("https", new
		// EasySSLSocketFactory(),
		// 443));

		HttpParams params = new BasicHttpParams();
		params.setParameter(ConnManagerPNames.MAX_TOTAL_CONNECTIONS, 30);
		params.setParameter(ConnManagerPNames.MAX_CONNECTIONS_PER_ROUTE,
				new ConnPerRouteBean(30));
		params.setParameter(HttpProtocolParams.USE_EXPECT_CONTINUE, false);
		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);

		ClientConnectionManager cm = new SingleClientConnManager(params,
				schemeRegistry);
		return new DefaultHttpClient(cm, params);
	}

	public static String getUserName() {
		return userName;
	}

	public static void setUserName(String userName) {
		DefaultRESTController.userName = userName;
	}

	public static String getPassword() {
		return password;
	}

	public static void setPassword(String password) {
		DefaultRESTController.password = password;
	}

	public String getBaseUrl() {
		return baseUrl;
	}

	public void setBaseUrl(String baseUrl) {
		this.baseUrl = baseUrl;
	}

	public boolean isWithAuthorization() {
		return withAuthorization;
	}

	public void setWithAuthorization(boolean withAuthorization) {
		this.withAuthorization = withAuthorization;
	}

}